package org.eclipse.soc.scripting.core;

import java.io.StringReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.script.ScriptException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IExecutableExtensionFactory;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.jruby.RubyException;
import org.jruby.exceptions.RaiseException;

/**
 * A "proxy" class acting as a common facade for all extension types. Should be
 * used in class attribute declaration as follows:
 * 
 * <pre>
 *   &lt;action
 *        class=&quot;org.eclipse.soc.scripting.core.ScriptExtensionProxy:org.eclipse.ui.IWorkbenchWindowActionDelegate/action.groovy&quot;
 *        icon=&quot;icons/sample.gif&quot;
 *        id=&quot;ScriptAction&quot;
 *        label=&quot;&amp;Sample Action&quot;
 *        menubarPath=&quot;sampleMenu/sampleGroup&quot;
 *        toolbarPath=&quot;sampleGroup&quot;
 *        tooltip=&quot;Hello, Eclipse world&quot;&gt;
 *   &lt;/action&gt;  
 * 
 * </pre>
 * 
 * @author ant
 * 
 */
public class ScriptExtensionProxy implements IExecutableExtension,
		InvocationHandler, IExecutableExtensionFactory {

	private static final String PLUG_IN_INTERNAL_ERROR = "Scripting Plug-In Internal Error";

	private URL script;

	private Class<?> implementable;

	private Map<String, Object> internals = new HashMap<String, Object>();

	private ScriptExecutor scriptExecutor;

	/**
	 * Initializes the scripting back-end of the current extension.
	 * 
	 * 
	 * @param config
	 *            configuration element handle
	 * @param propertyName
	 *            not used
	 * @param declares
	 *            an interface and a script implementation of this interface in
	 *            form of: interface/script
	 * 
	 * @see IExecutableExtension#setInitializationData(IConfigurationElement,
	 *      String, Object)
	 */
	@Override
	public void setInitializationData(IConfigurationElement config,
			String propertyName, Object data) {

		String[] entryData = data.toString().split("/");
		String interfaceName = entryData[0];
		String scriptFileName = entryData[1];

		try {
			implementable = Class.forName(interfaceName);
		} catch (Exception e) {
			MessageDialog.openInformation(
					Display.getCurrent().getActiveShell(),
					PLUG_IN_INTERNAL_ERROR,
					"error in setInitializationData() method: "
							+ e.getClass().getName() + ":" + e.getMessage());
			throw new RuntimeException(e);
		}

		this.script = Util.getInitializationData(config, scriptFileName);

		initializeScriptingBackend();
	}

	protected void initializeScriptingBackend() {
		scriptExecutor = new ScriptExecutor();
		try {
			scriptExecutor.evalScript(new StringReader(Util
					.getScriptContents(script)), script.getFile(), internals);
		} catch (Exception e) {
			MessageDialog.openInformation(
					Display.getCurrent().getActiveShell(),
					PLUG_IN_INTERNAL_ERROR, "Script " + script.getFile()
							+ " evaluation failed: " + e.getClass().getName()
							+ ": " + e.getMessage());
			throw new RuntimeException(e);
		}
	}

	/**
	 * Creates a proxy object that will delegate the methods calls to the script
	 * 
	 * @see IExecutableExtensionFactory#create()
	 */
	@Override
	public Object create() throws CoreException {
		return Proxy.newProxyInstance(implementable.getClassLoader(),
				new Class[] { implementable }, this);
	}

	/**
	 * Implements the delegation of proxy objects method call to the script
	 * 
	 * @see InvocationHandler#invoke(Object, Method, Object[])
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		try {							
			return scriptExecutor.invokeMethod(implementable, method, args, null);
		}
		catch (Exception e) {
			String message = "Invocation error for method " + method
			+ " with argument list " + Arrays.asList(args)
			+ ": " + e.getClass().getName() + ": "
			+ e.getMessage();
			
			error_message(e, message);
			throw e;
		}

	}
	
	private void error_message(Throwable e, String message)
	{
		if (e.getCause() instanceof RaiseException) {
			RaiseException re = (RaiseException) e.getCause();
			RubyException rubyE = re.getException();
			rubyE.backtrace();
		}
		MessageDialog.openInformation(
				Display.getCurrent().getActiveShell(),
				PLUG_IN_INTERNAL_ERROR,
				message);		
	}
	
}
